package org.terukusu.dropboxdownloader;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

import android.util.Log;

import com.dropbox.client.DropboxAPI;
import com.dropbox.client.DropboxAPI.Entry;
import com.dropbox.client.DropboxAPI.FileDownload;

/**
 * @author Teruhiko Kusunoki&lt;<a href="teru.kusu@gmail.com">teru.kusu@gmail.com</a>&gt;
 *
 */
public class DropboxManager {
    /**
     * TAG for {@link android.util.Log}
     */
    private static final String TAG = DropboxManager.class.getSimpleName();

    /**
     * Dropboxのルート
     */
    private static final String DROP_BOX_ROOT = "dropbox";

    /**
     * シングルトンインスタンス
     */
    private static DropboxManager instance;

    /**
     * シングルトンインスタンスを取得します。
     *
     * @return シングルトンインスタンス
     */
    public synchronized static DropboxManager getInstance() {
        if (instance == null) {
            instance = new DropboxManager();
        }

        return instance;
    }

    private final DropboxAPI dropBoxAPI = new DropboxAPI();

    /**
     * 新しいオブジェクトを生成します。
     */
    protected DropboxManager() {
    }

    /**
     * アカウント認証を行ないます
     * @param mail メールアドレス
     * @param password パスワード
     * @return 認証が成功すればtrue
     */
    public boolean authenticate(String mail, String password) {
        try {
            DropboxAPI api = getDropboxAPI();

            DropboxAPI.Config config = getConfig();

            int success = DropboxAPI.STATUS_NONE;
            api.authenticate(config, mail, password);
            success = config.authStatus;

            if (success != DropboxAPI.STATUS_SUCCESS) {
                Log.e(TAG, "authenticate failed: " + success);
                return false;
            }

            DropboxAPI.Account account = api.accountInfo();

            if (account.isError()) {
                Log.e(TAG, "Account info error: " + account.httpCode + " " + account.httpReason);
                return false;
            }

            Log.v(TAG, "tokenkey = " + config.accessTokenKey + ", tokenSecret = " + config.accessTokenSecret);

            return true;
        } catch (Exception e) {
            Log.e(TAG, "Error in logging in.", e);
            return false;
        }
    }

    /**
     * アカウント認証をトークン認証で行ないます。
     * @param tokenKey トークンキー
     * @param tokenSecret トークンシークレット
     * @return 認証が成功すればtrue
     */
    public boolean authenticateToken(String tokenKey, String tokenSecret) {
        try {
            DropboxAPI api = getDropboxAPI();

            DropboxAPI.Config config = getConfig();

            int success = DropboxAPI.STATUS_NONE;
            api.authenticateToken(tokenKey, tokenSecret, config);
            success = config.authStatus;

            if (success != DropboxAPI.STATUS_SUCCESS) {
                Log.e(TAG, "authenticate failed: " + success);
                return false;
            }

            DropboxAPI.Account account = api.accountInfo();

            if (account.isError()) {
                Log.e(TAG, "Account info error: " + account.httpCode + " " + account.httpReason);
                return false;
            }

            Log.v(TAG, "tokenkey = " + config.accessTokenKey + ", tokenSecret = " + config.accessTokenSecret);

            return true;
        } catch (Exception e) {
            Log.e(TAG, "Error in logging in.", e);
            return false;
        }
    }

    /**
     * 認証済状態を解除します
     */
    public void deauthenticate() {
        getDropboxAPI().deauthenticate();
    }

    /**
     * DropBoxの設定オブジェクトを取得します。
     *
     * @return DropBoxの設定オブジェクト
     */
    public DropboxAPI.Config getConfig() {
        DropboxAPI api = getDropboxAPI();

        DropboxAPI.Config config = api.getConfig(null, false);
        // TODO obfuscate keys
        config.consumerKey = new String(new char[]{119, 103, 113, 117, 98, 50, 115, 109, 110, 116, 49, 56, 119, 48, 102});
        config.consumerSecret = new String(new char[]{119, 112, 48, 50, 100, 55, 101, 118, 97, 98, 98, 98, 121, 102, 107});
        config.server="api.dropbox.com";
        config.contentServer="api-content.dropbox.com";
        config.port=80;

        return config;
    }

    /**
     * 指定したディレクトリの中身の一覧を取得します。
     * 指定したパスがディレクトリではなかった場合はからのリストを返します。
     *
     * @param dbPath DropBox上のパス
     * @return 指定したディレクトリの中身の一覧
     */
    public DropboxAPI.Entry getMetaData(String dbPath) {
        if ("/".equals(dbPath)) {
            dbPath = "";
        }

        // TODO 一度に取得するリストに上限を設定
        Entry entry = getDropboxAPI().metadata(DROP_BOX_ROOT, dbPath, 0, null, true);
        return entry;
    }

    /**
     * DropBox上のファイルをダウンロードします。
     *
     * @param dbPath ドロップボックス上のパス
     * @param localFile ローカルのパス
     * @return ローカルのパスを表すオブジェクト
     * @throws IOException
     */
    public File getFile(String dbPath, File localFile) throws IOException{
        if ("/".equals(dbPath)) {
            dbPath = "";
        }

        DropboxAPI api = getDropboxAPI();
        BufferedInputStream br = null;
        BufferedOutputStream bw = null;

        try {
            if (!localFile.exists()) {
                localFile.createNewFile();
            }

            FileDownload fd = api.getFileStream(DROP_BOX_ROOT, dbPath, null);
            br = new BufferedInputStream(fd.is);
            bw = new BufferedOutputStream(new FileOutputStream(localFile));

            byte[] buff = new byte[4096];
            for (int read = 0; (read = br.read(buff)) >= 0; ) {
                bw.write(buff, 0, read);
            }
        } finally {
            if (bw != null) {
                try { bw.close(); } catch (Exception ignore) {}
            }
            if (br != null) {
                try { br.close(); } catch (Exception ignore) {}
            }
        }

        return localFile;
    }

    /**
     * DropBoxの認証が完了しているかどうかを取得します。
     *
     * @return 認証されていれば true
     */
    public boolean isAuthenticated() {
        return getDropboxAPI().isAuthenticated();
    }

    /**
     * DropBoxのアカウント情報を取得します。
     *
     * @return DropBxoのアカウント情報
     */
    public DropboxAPI.Account getAccountInfo() {
        DropboxAPI api = getDropboxAPI();
        if (!api.isAuthenticated()) {
            return null;
        }
        return getDropboxAPI().accountInfo();
    }

    /**
     * DropBoxAPIを取得します。
     *
     * @return DropBoxAPI
     */
    protected DropboxAPI getDropboxAPI() {
        return this.dropBoxAPI;
    }
}
